package timer

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	kadefine "kiwi/actor/define"
)

type ID = uint64
type Tag = uint64

type Callback func(ctx kadefine.Context, id ID, tag Tag)

type MessageTimeout struct {
	ID  ID
	Tag Tag
}

var gMessageTimeoutPool = sync.Pool{}

func CreateMessageTimeout(id ID, tag Tag) *MessageTimeout {
	m, ok := gMessageTimeoutPool.Get().(*MessageTimeout)
	if ok && m != nil {
		m.ID = id
		m.Tag = tag
	} else {
		m = &MessageTimeout{
			ID:  id,
			Tag: tag,
		}
	}
	return m
}

func DestroyMessageTimeout(m *MessageTimeout) {
	if m == nil {
		return
	}
	m.ID = 0
	m.Tag = 0
	gMessageTimeoutPool.Put(m)
}

var gTagGenerator uint64

func NextTag() Tag {
	return atomic.AddUint64(&gTagGenerator, 1)
}

type timer struct {
	id     ID
	tag    ID
	cb     Callback
	looped bool
	stopCh chan struct{}
}

func (t *timer) stop() {
	close(t.stopCh)
}

func (t *timer) trigger(ctx kadefine.Context) {
	t.cb(ctx, t.id, t.tag)
}

type Manager struct {
	idGenerator uint64
	timers      *sync.Map
	rootContext *kadefine.RootContext
	pid         *kadefine.PID
}

func CreateManager(ctx kadefine.Context) *Manager {
	return &Manager{
		idGenerator: 0,
		timers:      &sync.Map{},
		rootContext: ctx.ActorSystem().Root,
		pid:         ctx.Self(),
	}
}

func (m *Manager) nextID() ID {
	return atomic.AddUint64(&m.idGenerator, 1)
}

func (m *Manager) AddOnce(dur time.Duration, tag Tag, callback Callback) ID {
	newTimer := &timer{
		id:     m.nextID(),
		tag:    tag,
		cb:     callback,
		looped: false,
		stopCh: make(chan struct{}),
	}

	m.timers.Store(newTimer.id, newTimer)

	go func() {
		select {
		case <-time.After(dur):
			m.rootContext.Send(m.pid, CreateMessageTimeout(newTimer.id, newTimer.tag))

		case <-newTimer.stopCh:
			return
		}
	}()

	return newTimer.id
}

func (m *Manager) AddLoop(dur time.Duration, tag Tag, callback Callback) ID {
	newTimer := &timer{
		id:     m.nextID(),
		tag:    tag,
		cb:     callback,
		looped: true,
		stopCh: make(chan struct{}),
	}

	m.timers.Store(newTimer.id, newTimer)

	go func() {
		ticker := time.NewTicker(dur)
		chTicker := ticker.C

		for {
			select {
			case <-chTicker:
				m.rootContext.Send(m.pid, CreateMessageTimeout(newTimer.id, newTimer.tag))

			case <-newTimer.stopCh:
				ticker.Stop()
				return
			}
		}
	}()

	return newTimer.id
}

func (m *Manager) Trigger(ctx kadefine.Context, id ID) error {
	v, ok := m.timers.Load(id)
	if !ok {
		return fmt.Errorf("timer not found")
	}
	t, _ := v.(*timer)
	t.trigger(ctx)
	if !t.looped {
		m.timers.Delete(id)
	}
	return nil
}

func (m *Manager) Stop(id ID) error {
	v, ok := m.timers.Load(id)
	if !ok {
		return fmt.Errorf("timer[%d] not found", id)
	}

	t, _ := v.(*timer)
	t.stop()

	m.timers.Delete(id)

	return nil
}

func (m *Manager) StopAll() {
	m.timers.Range(func(k, v any) bool {
		t, _ := v.(*timer)
		t.stop()
		return true
	})
	// 清空map
	m.timers = nil
}
